package com.gmrz.uaf.servlet.v1.handler;

import com.gmrz.service.challengestore.ChallengeStoreException;
import com.gmrz.service.challengestore.ChallengeStoreService;
import com.gmrz.uaf.common.Constants;
import com.gmrz.uaf.common.GuiceUtil;
import com.gmrz.uaf.protocol.v1.json.UAFSchemaBuilder;
import com.gmrz.uaf.protocol.v1.schema.AuthenticationRequest;
import com.gmrz.uaf.remote.protocol.v1.processor.UAPOOBAuthInitProcessor;
import com.gmrz.uaf.server.ServerContext;
import com.gmrz.uaf.servlet.v1.request.OOBAuthContext;
import com.gmrz.uaf.servlet.v1.request.ReceiveReqPayload;
import com.gmrz.uaf.servlet.v1.response.FidoRespPayload;
import com.gmrz.uaf.servlet.v1.validation.PayloadValidationException;
import com.gmrz.uaf.servlet.v1.validation.RestValidator;
import com.gmrz.util.Convert;
import com.gmrz.util.Strings;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.lang.reflect.Type;
import java.util.*;

public class UAPOOBAuthRequestHandler extends UAFBaseHandler {
    private static final Logger LOG = LogManager.getLogger(UAPOOBAuthRequestHandler.class);

    /**
     * {
     * "context":{
     * "userName":"test076",
     * "appID":"1103",
     * "transNo":"sssddddfffffggggghhhhheeee",
     * "transType":"00",
     * "authType":["00","14"],
     * "authToken":"0r2orLw8t_0cAjm5mGbGaomdVvUCfjjzhVUd9wdqBpA",
     * "devices":{"deviceName":"HUA WEI","deviceType":"iPhone 7","deviceID":"+xkVOHTXVzFOpP+YzyHsZhFbn4ldgiWXGZ\/eGL5NHB7hXTn46LeqOPfYQk+YO9QL"}
     * }
     * }
     *
     * @param request
     * @return {"statusCode":1200,"description":"OK","uafRequest":{"00":"{\"header\":{\"upv\":{\"major\":1,\"minor\":0},\"op\":\"Reg\",\"appID\":\"\",\"serverData\":\"DwdgrW4eFZWVMA5KSJXbrEAAS53O7YrJjGDYz3d0UTGfZTdGkpTimJ2mfRKye9zBYaMTWtciHGSFPieisIPERHwk-DKPGfzZ9YGXn6ymFypgiyYQvxw6H6F1QVZrHNDuQdk8m2evxsOhHSk5elrq_OLKZisU\"},\"challenge\":\"5Aw-8UDn1P6lm64j4s5nfSr_1aEwsnZNKkdkWzyTjeo\",\"username\":\"test076\",\"policy\":{}}","14":"{\"header\":{\"upv\":{\"major\":1,\"minor\":0},\"op\":\"Reg\",\"appID\":\"\",\"serverData\":\"MVA51O8JzkYugnmvnfUB-kK72UEDibyUbU4N4lSwlFwIolyf7k1h6U53D8M8Dnk6TPl4GN59f4vKjYhiQiT1j1bEc9DQfLEObhgzQaBBQuxAO9YgsLXmMPIQRfvYX3S3eGmbH2w_DVGaZNNeaU5kuRHEfYJ0\"},\"challenge\":\"-Q9wbJc0PW5LTugw46entXNmEn1l58dMUCBny96CzCY\",\"username\":\"test076\",\"policy\":{\"accepted\":[[{\"aaid\":[\"004A#01B4\"]}]]}}"}}
     */
    public String regInit(String request) {
        LOG.info("[/oob/receive][begin]");
        ServerContext.getServerContext().getFidoLog().setRequest(request);
        Gson gson = UAFSchemaBuilder.getGson();
        FidoRespPayload<Map<String, String>> respPayload = new FidoRespPayload<Map<String, String>>();
        Type reqType = new TypeToken<ReceiveReqPayload<OOBAuthContext>>() {
        }.getType();
        String response = null;
        try {
            ReceiveReqPayload<OOBAuthContext> reqPayload = gson.fromJson(request, reqType);
            ServerContext.getServerContext().setBase64(reqPayload.getContext().isBase64());
            List<String> authTypes = reqPayload.getContext().getAuthType();
            boolean isMultipleAuth = false;
            if(null != authTypes){
                for(String auth : authTypes){
                    if(!auth.equals(Constants.AuthType.REMOTE_OOBAUTH.getAuthType())){
                        isMultipleAuth = true;
                        break;
                    }
                }
            }
            if(!isMultipleAuth) {
                logRequest(request, reqPayload.getContext().getTransNo(), reqPayload.getContext().getUserName(),
                        reqPayload.getContext().getAppID(), Constants.AuthType.REMOTE_OOBAUTH.getAuthType(), reqPayload.getContext().getTransType(), Constants.Method.OOBAUTH_INIT);
            }
            //验证厂商白名单和设备黑名单
            validateDevice(reqPayload.getContext().getDevices(), reqPayload.getContext().getAppID());
            validate(reqPayload);
            UAPOOBAuthInitProcessor processor = GuiceUtil.getProcessorInjector()
                    .getInstance(UAPOOBAuthInitProcessor.class);
            OOBAuthContext context = reqPayload.getContext();
            // hash 用户名
            String hashedUserName = generateHashedUserName(context.getUserName(), context.getRf1(), context.getRf2());
            String custNo = context.getUserName();
            String challenge = context.getChallenge();
            byte[] challengeByte = null;
            if(Strings.isNotBlank(challenge)){
                challengeByte  = challenge.getBytes("utf-8");
            }else {
                challengeByte = new byte[0];
            }
            Map<String, AuthenticationRequest> requests = processor.request(hashedUserName, context.getAppID(), context.getAuthType(), context.getTransType(), context.getDevices().getDeviceID(), context.getAuthToken(),context.getAuthData(), custNo,context.getTransactionText(),challengeByte , context.getExt() );
            LOG.info("response=" + gson.toJson(requests));
            respPayload.setStatusCode(Constants.StatusCode.OK.getCode());
            respPayload.setDescription("OK");
            Map<String,String> uafRequestMap = new LinkedHashMap<String, String>();
            for(String authT: context.getAuthType()){
                List<AuthenticationRequest> list = new ArrayList<AuthenticationRequest>();
                list.add(requests.get(authT));
                if(ServerContext.getServerContext().isBase64()) {
                    uafRequestMap.put(authT, Convert.toBase64(gson.toJson(list).getBytes()));
                }else {
                    uafRequestMap.put(authT, gson.toJson(list));
                }
            }
            respPayload.setUafRequest(uafRequestMap);
            response = gson.toJson(respPayload);
            // 缓存发起接口的 context 参数
            saveContextToRedis(context, requests, hashedUserName);
        } catch (Exception ex) {
            LOG.error("regInit error",ex);
            response = buildFidoRespFromException(respPayload, ex);
        }
        LOG.info("[/oob/receive][end]");
        return response;
    }

    void validate(ReceiveReqPayload<OOBAuthContext> reqPayload) throws PayloadValidationException {
        if ((null == reqPayload) || (null == reqPayload.getContext())) {
            throw new PayloadValidationException("context", RestValidator.VALIDATION_ERROR.EMPTY);
        }
        OOBAuthContext context = reqPayload.getContext();
        RestValidator.validate("authToken", context.getAuthToken());
        RestValidator.validate("authData", context.getAuthData());

        RestValidator.validate("userName", context.getUserName());
        RestValidator.validate("appID", context.getAppID());
        List<String> authTypeList = context.getAuthType();
        if(null == authTypeList || authTypeList.size() <= 0){
            throw new PayloadValidationException("authType",RestValidator.VALIDATION_ERROR.EMPTY);
        }
        for (String auth : authTypeList) {
            RestValidator.validate("authType", auth);
        }
        RestValidator.validate("rf1", context.getRf1());
        RestValidator.validate("rf2", context.getRf2());
        RestValidator.validate("transType", context.getTransType());
        RestValidator.validate("deviceID", context.getDevices().getDeviceID());
        RestValidator.validate("transNo", context.getTransNo());

        String transactionText = context.getTransactionText();
        RestValidator.validate("transactionText", transactionText);
        if (!Strings.isNullOrEmpty(transactionText)) {
            byte[] txdata = Convert.fromBase64(transactionText);
            try {
                new String(txdata, "UTF-8");
            } catch (Exception e) {
                LOG.error(e);
                throw new PayloadValidationException("transactionText",
                        RestValidator.VALIDATION_ERROR.INVALID_CHARACTERS);
            }
        }
        RestValidator.validateDevices(context.getDevices());
    }

    /**
     * 缓存发起接口的 context 参数
     * @param context
     * @param requests
     * @param hashedUserName
     * @throws ChallengeStoreException
     */
    private void saveContextToRedis(OOBAuthContext context, Map<String,AuthenticationRequest> requests, String hashedUserName) throws ChallengeStoreException {
        Iterator<Map.Entry<String, AuthenticationRequest>> it =  requests.entrySet().iterator();
        List<String> authTypeList =  new ArrayList<String>();
        authTypeList.add(Constants.AuthType.REMOTE_OOBAUTH.getAuthType());
        while (it.hasNext()) {
            Map.Entry<String, AuthenticationRequest> en = it.next();
            if(null != en.getValue() && null != en.getValue().getOperationHeader() && null != en.getValue().getOperationHeader().getServerData() && null != en.getValue().getOperationHeader().getServerData().getChallenge()) {
                String challenge = Convert.toBase64(en.getValue().getOperationHeader().getServerData().getChallenge());
                String param = getValidateData(context.getUserName(), context.getRf1(), context.getRf2(),
                        context.getAppID(), null, context.getTransType(), authTypeList,null);
                ChallengeStoreService challengeStoreService = GuiceUtil.getProcessorInjector().getInstance(ChallengeStoreService.class);
                param = param + ",authToken=" + context.getAuthToken() + ",authData=" + context.getAuthData();
                challengeStoreService.setRedisValue(Constants.UAP_AUTH_CONTEXT_CACHE_KEY + challenge + "-" + hashedUserName, 300, param);

            }
        }
    }
}
